//
// Unihan Input Method
//
// Copyright © 2002-2007 Sun Wah Linux Ltd.
//
//
// This library is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation; either version 2.1 of the
// License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
// USA
//
// $Id: xaux_ext_lookup.c 798 2007-07-25 02:51:14Z roger $
//
/*
** $Id: xaux_ext_lookup.c 798 2007-07-25 02:51:14Z roger $
**
** SWin X Auxilliary Objects
** External module -- IM preedit/lookup
*/

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif /* HAVE_CONFIG_H */

#ifdef DEBUG
#define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__);
#else
#define DEBUG_LOG(...)
#endif

#define USE_PREEDIT_AUX 1
#define PREEDIT_HEIGHT 22
#define CANDS_MAX 10

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libintl.h>

#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gdk-pixbuf/gdk-pixdata.h>

#include <gconf/gconf-client.h>

#include <libsoup/soup.h>
#include <libsoup/soup-headers.h>

#include <X11/X.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>

#include "xaux_common.h"
#include "xaux_ext_common.h"

#include "json.h"

#include "unihan_xaux.h"
#include "punc_keyboard.h"
#include "us_keyboard.h"

#include "imlabel.h"
#include "gtkflatbutton.h"

#define _(x) gettext (x)
#define N_(x) (x)

#define PREEDIT_FONT	"Sans 12"
#define PREEDIT_FG	"#000000"
#define PREEDIT_BG	"#EDE9E3"
#define CANDIDATE_FONT	"Sans 12"
#define CANDIDATE_FG	"#000000"
#define CANDIDATE_BG	"#EDE9E3"
#define NUMBER_FONT	"Monospace Bold 9"
#define NUMBER_FG	"#3E808D"
#define	NUMBER_BG	"#FFFFFF"
#define STATUS_FONT	"Sans 9"
#define STATUS_FG	"#FF0000"
#define STATUS_BG	"#FFFFFF"

#define VOID_IM_ID 0xFFFF
#define VOID_IC_ID 0xFFFF

#define OPACITY_PROP    "_NET_WM_WINDOW_OPACITY"
#define CANDIDATE_ALPHA 0xc0000000 /* 0.75 of 0xffffffff */

#include "unihan-gconf.h"

#define USE_PROXY_KEY   "/system/http_proxy/use_http_proxy"
#define PROXY_HOST_KEY  "/system/http_proxy/host"
#define PROXY_PORT_KEY  "/system/http_proxy/port"
#define USE_PROXY_AUTH  "/system/http_proxy/use_authentication"
#define PROXY_USER      "/system/http_proxy/authentication_user"
#define PROXY_PASSWORD  "/system/http_proxy/authentication_password"

typedef enum {
	GRAVITY_TOP = 0,
	GRAVITY_BOTTOM = 1,
} WindowGravity;

typedef struct {
	GtkWidget *window;
	GtkWidget *ebox;	
	GtkWidget *child;
	gboolean poppedup;
	GdkColor fg;
	GdkColor bg;
	int x;
	int y;
	int width;
	int height;
	WindowGravity gravity;
} WindowData;

typedef struct {
	GtkWidget *window;
	GtkWidget *label;
	guint timer_tag;
} TooltipData;

typedef struct {
	int num_cands;
	GtkWidget *cand[CANDS_MAX];
	GtkWidget *button[CANDS_MAX];
    GtkWidget *title;
	gboolean have_prev;
	gboolean have_next;
	gboolean is_phrase;
	int total_cands;
	int lu_curr_cand;
	int selected_item;
    int cands_per_page;
} LookupData;

typedef enum _UnihanTCSCMode {
        UNIHAN_TCSC_NO_CONV,
        UNIHAN_TCSC_TO_SIMP,
        UNIHAN_TCSC_TO_TRAD
} UnihanTCSCMode;


extern Bool xaux_ext_SetValue(Display *, xaux_class_t *, aux_ext_data_t *);

Bool xaux_ext_Start(xaux_class_t *, aux_ext_data_t *, Atom atom);
Bool xaux_ext_Draw(xaux_class_t *, aux_ext_data_t *, Atom atom);
Bool xaux_ext_Done(xaux_class_t *, aux_ext_data_t *, Atom atom);
static LookupData *parse_lookup_values(aux_ext_data_t *);
static void draw_preedit(int, int, char *, int);
static void draw_lu_horz(int, int, const LookupData *);
static void draw_lu_vert(int, int, const LookupData *);
static void reposition_lu_vert(int, int);
static void reposition_lu_horz(int, int);
static void hide_preedit(void);
static void hide_lookup(void);
static GdkFilterReturn xaux_ext_event_handler(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer user_data);
void send_to_engine(int, int *, int, char **);
static void unparent_candidates(void);
static void propagate_default_im_change(const gchar *);
static void default_im_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void propagate_tc_sc_change(const int);
static void tc_sc_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void propagate_phrase_mode_change(const gboolean);
static void preedit_color_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void candidate_color_changed(GConfClient *, guint, GConfEntry *, gpointer);
//static void candidate_alpha_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void number_color_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void status_color_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void im_option_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void candidate_direction_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void propagate_im_option_change(const gchar *, const gchar *, const gchar *);
static gint tooltip_timeout(gpointer);
static void hide_tooltip(void);
static void draw_tooltip(int, int, char *);
static gint tooltip_paint_window(GtkTooltips *);
static void init_tooltip_window(void);
static gboolean candidate_button_pressed_cb(GtkWidget *, gpointer);
static gboolean candidate_page_pressed_cb(GtkWidget *, gpointer);
static LookupData *google_suggest_lookup_data_init(void);
static void google_suggest_get(char *);
static void google_suggest_received(SoupMessage *, gpointer);
static void update_use_google_suggest(void);
static void phrase_mode_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void proxy_changed(GConfClient *, guint, GConfEntry *, gpointer);
static void set_proxy(SoupSession *);

#define ATOM_NUM_MODULES	32
/* Define Aux class */
xaux_class_t xaux_class = {
	AUX_LOOKUP_CLASS_NAME, 1,
	UNIHAN_XAUX_DIR "/gnome/xaux_ext_lookup",
	(Window)0, (Window)0, (Window)0,
	(Atom)0, (Atom)0, (Atom)0, (Atom)0,
	{ (Atom)0 }, ATOM_NUM_MODULES, 0, 
	{ (Atom)0 }, ATOM_NUM_MODULES, 0,
	NULL,
};
static xaux_class_t *xc_ = NULL;
static int im_ = VOID_IM_ID;
static int ic_ = VOID_IC_ID;
static WindowData *preedit = NULL, *lu_vert = NULL, *lu_horz = NULL;
static TooltipData *tooltip = NULL;
static PangoAttrList *cand_num_attr = NULL;
static PangoAttrList *phr_num_attr = NULL;
static PangoAttrList *hi_cand_num_attr = NULL;
static PangoAttrList *hi_phr_num_attr = NULL;
static LookupData *lookup_data;
static Display *display;
static GtkWidget *phrase_hint;
static GtkWidget *up_arrow, *down_arrow, *left_arrow, *right_arrow;
static GtkWidget *lu_horz_status_label, *lu_vert_status_label;
static GtkWidget *bar_horz, *bar_vert;
static gboolean _candidate_horizontally = TRUE;

static SoupSession *_soupsession = NULL;
static LookupData *_gsuggest_lookup_data = NULL;
static gboolean _use_google_suggest = TRUE;

#if 0
static void
set_opacity_prop(GdkWindow *w, unsigned int alpha)
{
        Display *dpy = GDK_WINDOW_XDISPLAY(w);
        Window id = GDK_WINDOW_XWINDOW(w);

        XChangeProperty(dpy, id, XInternAtom(dpy, OPACITY_PROP, False), XA_CARDINAL, 32, PropModeReplace, (unsigned char *)&alpha, 1L);
        XSync(dpy, False);
}
#endif

Bool
xaux_ext_Start(xaux_class_t *xc, aux_ext_data_t *aux_ext_data, Atom atom)
{
	DEBUG_LOG("xaux lookup: %s\n", __func__);

	GConfClient *client = gconf_client_get_default();
	gchar *default_im;
	int tc_sc_mode;
	gboolean phrase_mode;
        GSList *imList;
	int int_values[1];

	im_ = aux_ext_data->im;
	ic_ = aux_ext_data->ic;
	xc_ = xc;

	/* tell them we're here */
	int_values[0] = UNIHAN_AUX_LOOKUP_START_NOTIFY;
	send_to_engine(1, int_values, 0, NULL);

        /* tell the server about our preferences */
	default_im = gconf_client_get_string(client, DEFAULT_IM_KEY, NULL);
	if (default_im)
		propagate_default_im_change(default_im);
	else
		propagate_default_im_change("itabc");
	tc_sc_mode = gconf_client_get_int(client, TC_SC_KEY, NULL);
	propagate_tc_sc_change(tc_sc_mode);
	phrase_mode = gconf_client_get_bool(client, PHRASE_MODE_KEY, NULL);
	propagate_phrase_mode_change(phrase_mode);
       
        imList = gconf_client_all_dirs(client, UNIHAN_ROOT_KEY "/im-options", NULL);
        while (imList) {
                DEBUG_LOG("going through %s\n", (char *)imList->data);

                GSList *keyList = gconf_client_all_entries(client, (char *)imList->data, NULL);
                while (keyList) {
                        GConfEntry *kentry = (GConfEntry *)keyList->data;

                        DEBUG_LOG("found key %s\n", gconf_entry_get_key(kentry));

                        im_option_changed(client, 0, kentry, NULL);

                        keyList = keyList->next;
                }
                imList = imList->next;
        }

	return True;
}

static LookupData *
parse_lookup_values(aux_ext_data_t *aux_ext_data)
{
	LookupData *d = lookup_data;
	int i;

	d->have_prev = aux_ext_data->integer_list[3] & 2;
	d->have_next = aux_ext_data->integer_list[3] & 1;
	d->is_phrase = aux_ext_data->integer_list[2];

	d->total_cands = aux_ext_data->integer_list[4];
	d->lu_curr_cand = aux_ext_data->integer_list[5];
	d->selected_item = aux_ext_data->integer_list[6];
    d->cands_per_page = aux_ext_data->integer_list[7];

	d->num_cands = aux_ext_data->integer_list[1];

    char *title = aux_ext_data->string_list[0].ptr;
    if (title && strlen(title)) {
        gtk_label_set_text(GTK_LABEL(d->title), title);
        gtk_widget_show(d->title);
    } else {
        gtk_widget_hide(d->title);
    }

	int j = 1;
	for (i = 0; i < d->num_cands && i < CANDS_MAX; ++i, j+=2) {
		char *candstr;
		candstr = g_strconcat((gchar*)aux_ext_data->string_list[j].ptr, ": ",
		                      (gchar*)aux_ext_data->string_list[j+1].ptr, " ", NULL);
		gtk_label_set_text(GTK_LABEL(d->cand[i]), candstr);
		g_free(candstr);
	}

	return d;
}

static void
get_caret_point(aux_ext_data_t *aux_ext_data, GdkPoint *return_p)
{
        XWindowAttributes attr;
        int client_x = 0, client_y = 0;
	Window dummy;
	Window clientwindow = aux_ext_data->focuswin;
 
 	if (clientwindow && gdk_display) {
		gdk_error_trap_push();
		XGetWindowAttributes(gdk_display, clientwindow, &attr);
		gdk_flush();
		if (gdk_error_trap_pop() != 0)
			goto done_getting_clientwin_pos;

		gdk_error_trap_push();
		if (attr.root)
			XTranslateCoordinates(gdk_display, clientwindow, attr.root, -(attr.border_width), -(attr.border_width), &client_x, &client_y, &dummy);
		gdk_flush();
		if (gdk_error_trap_pop() != 0)
			goto done_getting_clientwin_pos;
	}

#ifdef DEBUG
        /*g_message("client (%d, %d) point (%d, %d) attr (%d, %d)", 
                  client_x, client_y, auxpoint.x,
                  auxpoint.y, attr.x, attr.y);*/
#endif

        if (aux_ext_data->point.y != -1) {  // -1 is invalid
                client_x += aux_ext_data->point.x;
                client_y += aux_ext_data->point.y + PREEDIT_HEIGHT; // add preedit height
		//p.y = client_y;
        } else {
                //client_x = client_x;
                client_y += attr.height;
		//p.y = client_y;
        }
done_getting_clientwin_pos:
	if ((client_x == 0) && (client_y == 0)) {		// invalid position
		// FIXME: don't hardcode
		client_x = 10;
		client_y = gdk_screen_height() - (preedit->height * 4);
	}

        return_p->x = client_x;
        return_p->y = client_y;
}


Bool
xaux_ext_Draw(xaux_class_t *xc, aux_ext_data_t *aux_ext_data, Atom atom)
{
	DEBUG_LOG("xaux lookup: %s\n", __func__);

	int type;
	LookupData *lu_data;
        GdkPoint p;
	
	im_ = aux_ext_data->im;
	ic_ = aux_ext_data->ic;
	xc_ = xc;

	//atoms = XListProperies();

	if (aux_ext_data->integer_count < 1)
		return False;

	type = aux_ext_data->integer_list[0];
	switch (type) {
		case UNIHAN_AUX_PREEDIT_DRAW: 
            // stop any Google Suggest queries still going
            soup_session_abort(_soupsession);
            {
                if (aux_ext_data->string_count < 1)
                    return False;

                hide_tooltip();
                get_caret_point(aux_ext_data, &p);
                int preedit_caret = -1;
                if (aux_ext_data->integer_count >= 2)
                    preedit_caret = aux_ext_data->integer_list[1];
                draw_preedit(p.x, p.y, (char*)aux_ext_data->string_list[0].ptr, preedit_caret);
            }
			break;

        case UNIHAN_AUX_PREEDIT_CARET:
            {
                if (aux_ext_data->integer_count < 2)
                    return False;
                int preedit_caret = aux_ext_data->integer_list[1];
                if (preedit_caret < 0) {
                    preedit_caret = strlen(im_label_get_text(IM_LABEL(preedit->child)));
                } else {
                    // we need to find where the n'th character is, in bytes
                    const gchar *pestr = im_label_get_text(IM_LABEL(preedit->child));
                    const gchar *s = pestr;
                    int i = preedit_caret;
                    while (i && s && *s) {
                        s = g_utf8_next_char(s);
                        --i;
                    }
                    preedit_caret = s - pestr;
                }
                im_label_set_caret_index(IM_LABEL(preedit->child), preedit_caret);
            }
            break;

		case UNIHAN_AUX_PREEDIT_DONE:
			hide_preedit();
			break;

		case UNIHAN_AUX_PREEDIT_HIDE:
			hide_preedit();
			break;

		case UNIHAN_AUX_LOOKUP_DRAW:
			if (aux_ext_data->integer_count < 8)
				return False;
			if (aux_ext_data->string_count < 1)
				return False;

			hide_tooltip();
			lu_data = parse_lookup_values(aux_ext_data);
			get_caret_point(aux_ext_data, &p);
			if (_candidate_horizontally && !lu_data->is_phrase)
				draw_lu_horz(p.x, p.y, lu_data);
			else
				draw_lu_vert(p.x, p.y, lu_data);
			break;

		case UNIHAN_AUX_LOOKUP_HIDE:
		case UNIHAN_AUX_LOOKUP_DONE:
			hide_lookup();
			break;

        case UNIHAN_AUX_COMMITTED_STRING:
            if (aux_ext_data->string_count < 1)
                return False;

            // stop any Google Suggest queries still going
            soup_session_abort(_soupsession);
            if (_use_google_suggest)
                google_suggest_get((char *)(aux_ext_data->string_list[0].ptr));
            break;

		case UNIHAN_AUX_TOOLTIP_DRAW:
			if (aux_ext_data->string_count < 1)
				return False;

            // stop any Google Suggest queries still going
            soup_session_abort(_soupsession);
			get_caret_point(aux_ext_data, &p);
			draw_tooltip(p.x, p.y, _((char *)(aux_ext_data->string_list[0].ptr)));
			break;

		case UNIHAN_AUX_TOOLTIP_FULLWIDTH:
			get_caret_point(aux_ext_data, &p);
			draw_tooltip(p.x, p.y, _("Fullwidth"));
			break;
		case UNIHAN_AUX_TOOLTIP_HALFWIDTH:
			get_caret_point(aux_ext_data, &p);
			draw_tooltip(p.x, p.y, _("Halfwidth"));
			break;
		case UNIHAN_AUX_TOOLTIP_ENGLISHPUNC:
			get_caret_point(aux_ext_data, &p);
			draw_tooltip(p.x, p.y, _("English punctuation"));
			break;
		case UNIHAN_AUX_TOOLTIP_CHINESEPUNC:
			get_caret_point(aux_ext_data, &p);
			draw_tooltip(p.x, p.y, _("Chinese punctuation"));
			break;

		case UNIHAN_AUX_TOOLTIP_TCSC_MODE:
			if (aux_ext_data->integer_count < 2)
				return False;

			{
				get_caret_point(aux_ext_data, &p);

				UnihanTCSCMode tcsc = aux_ext_data->integer_list[1];
				gchar *str;
				switch (tcsc) {
					case UNIHAN_TCSC_TO_SIMP:
						str = _("Traditional -> simplified");
						break;
					case UNIHAN_TCSC_TO_TRAD:
						str = _("Simplified -> traditional");
						break;
					default:
						str = _("No conversion");
				}
				draw_tooltip(p.x, p.y, str);
			}
			break;

		case UNIHAN_AUX_PUNC_KEYBOARD_SHOW:
			show_punc_keyboard();
			break;

		case UNIHAN_AUX_PUNC_KEYBOARD_HIDE:
			hide_punc_keyboard();
			break;

		case UNIHAN_AUX_US_KEYBOARD_SHOW:
			show_us_keyboard();
			if (aux_ext_data->integer_count < 3)
				return False;
			set_us_keyboard_state(aux_ext_data->integer_list[1], aux_ext_data->integer_list[2]);
			break;

		case UNIHAN_AUX_US_KEYBOARD_HIDE:
			hide_us_keyboard();
			break;
		default:
			break;
	}

	return True;
}

Bool
xaux_ext_Done(xaux_class_t *xc, aux_ext_data_t *aux_ext_data, Atom atom)
{
	DEBUG_LOG("xaux_ext_Done!");
	gtk_main_quit();
	return True;
}

static GdkFilterReturn
xaux_ext_event_handler(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer user_data)
{
	XEvent *xevent = (XEvent *) gdk_xevent;

        switch (xevent->type) {
                case ClientMessage:
			gdk_error_trap_push();
                        xaux_ext_process_client_message((Display *)gdk_display,
                                (XClientMessageEvent *) xevent);
			gdk_error_trap_pop();
                        break;
                case SelectionClear:
                        break;
        }
        return GDK_FILTER_CONTINUE;
}

static void
draw_preedit(int x, int y, char *string, int caret)
{
	GtkRequisition req;
	int screen_w, screen_h;
	int newx = x, newy = y;

	im_label_set_text(IM_LABEL(preedit->child), string);
    if (caret < 0)
        caret = strlen(string);
	im_label_set_caret_index(IM_LABEL(preedit->child), caret);

	// minimum 50 pixels wide
	gtk_window_resize(GTK_WINDOW(preedit->window), 50, 1);
	gtk_widget_size_request(preedit->window, &req);

	// don't draw it where the user can't see it ...
	screen_w = gdk_screen_width();
	screen_h = gdk_screen_height();
	if (newx + req.width >= screen_w) {
		newx = screen_w - req.width;
		// but don't overdo it
		if (newx < 0) newx = 0;
	}
	if (newy + req.height >= screen_h) {
		newy -= (PREEDIT_HEIGHT + req.height);
	}

	preedit->x = newx;
	preedit->y = newy;

	gtk_window_move(GTK_WINDOW(preedit->window), newx, newy);

	if (!preedit->poppedup) {
		preedit->poppedup = TRUE;
		gtk_widget_show(preedit->window);

		// init values for the first time, but afterwards update 
		// the stored dimensions in configure event as that is 
		// much more accurate
		preedit->width = req.width;
		preedit->height = req.height;
	}
}

static void
unparent_candidates(void) 
{
    GtkWidget *w, *p;
	int i;
	for (i = 0; i < CANDS_MAX; i++) {
		w = lookup_data->button[i];
		p = gtk_widget_get_parent(w);
		if (p != NULL) {
			gtk_container_remove(GTK_CONTAINER(p), w);
		}
	}
    w = lookup_data->title;
    p = gtk_widget_get_parent(w);
    if (p) {
        gtk_container_remove(GTK_CONTAINER(p), w);
    }
}

static void
draw_lu_horz(int x, int y, const LookupData *data)
{
	//GList *hbox_child, *hbox_children;
	GtkWidget *hbox = lu_horz->child;
	int i;
	int total_pages, curr_page;
	gchar *status;

	/* then add them to the box */
	for (i = 0; i < data->num_cands && i < CANDS_MAX; i++) {
		if ( i == data->selected_item ) {
			gtk_label_set_attributes(GTK_LABEL(data->cand[i]), 
						 hi_cand_num_attr);
		} else {
			gtk_label_set_attributes(GTK_LABEL(data->cand[i]), 
						 cand_num_attr);
		}
		if (gtk_widget_get_parent(data->button[i]) == NULL) {
			gtk_box_pack_start_defaults(GTK_BOX(hbox),data->button[i]);
		} else {
			unparent_candidates();
			gtk_box_pack_start_defaults(GTK_BOX(hbox),data->button[i]);
		}
		gtk_widget_show(data->button[i]);
	}

	for (i=data->num_cands; i>0 && i<CANDS_MAX; i++) {
		gtk_widget_hide(data->button[i]);
	}

	total_pages = (data->total_cands-1) / data->cands_per_page + 1;
	curr_page = data->lu_curr_cand / data->cands_per_page + 1;
	status = g_strdup_printf("%d/%d", curr_page, total_pages);
	gtk_label_set_text(GTK_LABEL(lu_horz_status_label), status);
	g_free(status);

	if (data->have_next)
		gtk_widget_show(right_arrow);
	else
		gtk_widget_hide(right_arrow);

	if (data->have_prev)
		gtk_widget_show(left_arrow);
	else
		gtk_widget_hide(left_arrow);

	if (total_pages > 1) {
		gtk_widget_show(lu_horz_status_label);
		gtk_widget_show(bar_horz);
	} else {
		gtk_widget_hide(bar_horz);
	}

	if (!preedit->poppedup || !lu_horz->poppedup)
		reposition_lu_horz(x, y);

	if (!lu_horz->poppedup) {
		lu_horz->poppedup = TRUE;
		gtk_widget_show(lu_horz->window);
	}
}

static void
draw_lu_vert(int x, int y, const LookupData *data)
{
	//GList *vbox_child, *vbox_children;
	GtkWidget *vbox = lu_vert->child;
	int i;
	int curr_page, total_pages;
	char *statstr;

	/* then add them to the box */
    unparent_candidates();
    gtk_box_pack_start(GTK_BOX(vbox), data->title, TRUE, TRUE, 0);
	for (i = 0; i < data->num_cands && i < CANDS_MAX; i++) {
		if ( i == data->selected_item ) {
			gtk_label_set_attributes(GTK_LABEL(data->cand[i]), 
						 (data->is_phrase) ?
						    hi_phr_num_attr : hi_cand_num_attr);
		} else {
			gtk_label_set_attributes(GTK_LABEL(data->cand[i]), 
						 (data->is_phrase) ?
						    phr_num_attr : cand_num_attr);
		}

		gtk_box_pack_start(GTK_BOX(vbox), data->button[i], TRUE, TRUE, 0);
		gtk_widget_show(data->button[i]);
	}
	/*for (i=data->num_cands; i>0 && i<CANDS_MAX; i++) {
		gtk_widget_hide(data->button[i]);
	}*/

	total_pages = (data->total_cands-1) / data->cands_per_page + 1;
	curr_page = data->lu_curr_cand / data->cands_per_page + 1;
	statstr = g_strdup_printf("%d/%d", curr_page, total_pages);
	gtk_label_set_text(GTK_LABEL(lu_vert_status_label), statstr);
	g_free(statstr);

	if (data->have_next)
		gtk_widget_show(down_arrow);
	else
		gtk_widget_hide(down_arrow);

	if (data->have_prev)
		gtk_widget_show(up_arrow);
	else
		gtk_widget_hide(up_arrow);

	if (total_pages > 1) {
		gtk_widget_show(lu_vert_status_label);
		gtk_widget_show(bar_vert);
	} else {
		gtk_widget_hide(bar_vert);
	}

	// don't jump around if already shown
	if (!lu_vert->poppedup) {
                lu_vert->x = x;
	}
	if (!preedit->poppedup || !lu_vert->poppedup)
		reposition_lu_vert(lu_vert->x, y);

        if (!lu_vert->poppedup) {
		lu_vert->poppedup = TRUE;
		gtk_widget_show(lu_vert->window);
        }
}

static void
reposition_lu_vert(int x, int y)
{
	GtkRequisition req;
	int screen_w = 0, screen_h = 0;
	int newx = x, newy = y;

	if (preedit->poppedup) {
		newy = preedit->y;
		newx = preedit->x + preedit->width + 1;
	}

	/* force resize of window */
	gtk_window_resize(GTK_WINDOW(lu_vert->window), 75, 100);
	/* see how much space we request */
	gtk_widget_size_request(lu_vert->window, &req);
	
	screen_w = gdk_screen_width();
	screen_h = gdk_screen_height();

#if USE_PREEDIT_AUX   // Since we don't draw preedit window anymore, we need a new algorithm
	if (preedit->poppedup) {
		int height_over = (newy + req.height) - screen_h;
		if (height_over > 0)
			newy -= height_over;

		int width_over = (newx + req.width) - screen_w;
		if (width_over > 0) {
			if ((preedit->width + req.width) > screen_w)  // trouble
				width_over = preedit->x;

			newx -= width_over;
			if (preedit->poppedup) {
				preedit->x -= width_over;
				gtk_window_move(GTK_WINDOW(preedit->window), preedit->x, preedit->y);
			}
		}
	} else {
#endif
		// if already popped up & gravity is bottom, 
		// pin the window to the existing bottom
		if (lu_vert->poppedup && GRAVITY_BOTTOM == lu_vert->gravity) {
			newy = lu_vert->y + lu_vert->height - req.height;
		} else {
			// if window will be outside screen,
			// reposition above the preedit and mark gravity bottom
			if (newy + req.height >= screen_h) {
				newy -= (PREEDIT_HEIGHT + 4 + req.height);
				lu_vert->gravity = GRAVITY_BOTTOM;
			}
		}

		if (newx + req.width >= screen_w) {
			newx = screen_w - req.width;
			if (newx < 0) newx = 0;
		}
#if USE_PREEDIT_AUX
	}
#endif

        lu_vert->x = newx;
	lu_vert->y = newy;
	lu_vert->height = req.height;
	lu_vert->width = req.width;
	gtk_window_move(GTK_WINDOW(lu_vert->window), newx, newy);
}

static void
reposition_lu_horz(int x, int y)
{
	GtkRequisition req;
	int screen_w = 0, screen_h = 0;
	int newx = x, newy = y;
	int width_over = 0;//, height_over = 0;

	if (preedit->poppedup) {
		newx = preedit->x;
		newy = preedit->y + preedit->height + 1;
	}

	/* force resize of window */
	gtk_window_resize(GTK_WINDOW(lu_horz->window), 50, 1);
	/* see how much space we request */
	gtk_widget_size_request(lu_horz->window, &req);
	
	screen_w = gdk_screen_width();
	screen_h = gdk_screen_height();

	width_over = (newx + req.width) - screen_w;
	if (width_over > 0)
		newx -= width_over;

	// FIXME: consider height_over case

	lu_horz->x = newx;
	lu_horz->y = newy;
	gtk_window_move(GTK_WINDOW(lu_horz->window), newx, newy);
}

static void
hide_preedit(void)
{
	if (!GTK_WIDGET_MAPPED(preedit->window))
		return;
	gtk_widget_hide(preedit->window);
	preedit->poppedup = FALSE;
}

static void
hide_lookup(void)
{
	if (GTK_WIDGET_MAPPED(lu_horz->window)) {
		gtk_widget_hide(lu_horz->window);
		lu_horz->poppedup = FALSE;
	}
	if (GTK_WIDGET_MAPPED(lu_vert->window)) {
		gtk_widget_hide(lu_vert->window);
		lu_vert->poppedup = FALSE;
		lu_vert->gravity = GRAVITY_TOP;
	}
}

static PangoFontDescription *
get_pango_fontdesc(const gchar *fontstr, const gchar *default_font)
{
	PangoFontDescription *fontdesc = NULL;
	if (fontstr)
		fontdesc = pango_font_description_from_string(fontstr);
	if (!fontdesc)
		fontdesc = pango_font_description_from_string(default_font);
	return fontdesc;
}

static void
candidate_font_changed(GConfClient *client,
		       guint cnxn_id,
		       GConfEntry *entry,
		       gpointer data)
{
	int i;
	const gchar *fontstr = NULL;
	PangoFontDescription *fontdesc = NULL;
	GConfValue *value = gconf_entry_get_value(entry);
	if (value != NULL)
		fontstr = gconf_value_get_string(value);
	fontdesc = get_pango_fontdesc(fontstr, CANDIDATE_FONT);
	for (i = 0; i < CANDS_MAX; i++) {
		gtk_widget_modify_font(lookup_data->cand[i], fontdesc);
	}
}

static void
init_lookup_data(GConfClient *client)
{
	DEBUG_LOG("xaux : init lookup data\n");

	PangoFontDescription *fontdesc = NULL;
	gchar *fontstr, *colorstr;
	GdkColor color;
	LookupData *d;
	int i;

	d = g_new0(LookupData, 1);

	fontstr = gconf_client_get_string(client, CANDIDATE_FONT_KEY, NULL);
	fontdesc = get_pango_fontdesc(fontstr, CANDIDATE_FONT);

	colorstr = gconf_client_get_string(client, CANDIDATE_FG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &color)) {
		gdk_color_parse(CANDIDATE_FG, &color);
	}
	g_free(colorstr);

	for (i = 0; i < CANDS_MAX; i++) {
		d->button[i] = gtk_flat_button_new();
		g_object_ref(d->button[i]);
		gtk_flat_button_set_alignment(GTK_FLAT_BUTTON(d->button[i]), 0, 0.5);
		GTK_WIDGET_UNSET_FLAGS(d->button[i], GTK_CAN_FOCUS);
		g_signal_connect(G_OBJECT(d->button[i]), "clicked", G_CALLBACK(candidate_button_pressed_cb), GINT_TO_POINTER(i));

		d->cand[i] = gtk_label_new(NULL);
		gtk_widget_modify_font(d->cand[i], fontdesc);
		gtk_widget_modify_fg(d->cand[i], GTK_STATE_NORMAL, &color);
		gtk_misc_set_alignment(GTK_MISC(d->cand[i]), 0.0, 0.5);
        gtk_misc_set_padding(GTK_MISC(d->cand[i]), 2, 0);
        gtk_container_set_border_width(GTK_CONTAINER(d->button[i]), 0);
		gtk_container_add(GTK_CONTAINER(d->button[i]), d->cand[i]);
		gtk_widget_show(d->cand[i]);

		gtk_widget_show(d->button[i]);
	}

    d->title = gtk_label_new(NULL);
    g_object_ref(d->title);

	lookup_data = d;
}

static void
real_init_attributes(PangoFontDescription *fontdesc, GdkColor *fg, GdkColor *bg)
{
	PangoAttribute *attr = NULL;

	if (cand_num_attr)
		pango_attr_list_unref(cand_num_attr);
	if (phr_num_attr)
		pango_attr_list_unref(phr_num_attr);
	if (hi_cand_num_attr)
		pango_attr_list_unref(hi_cand_num_attr);
	if (hi_phr_num_attr)
		pango_attr_list_unref(hi_phr_num_attr);
		
	cand_num_attr = pango_attr_list_new();
	phr_num_attr = pango_attr_list_new();
	hi_cand_num_attr = pango_attr_list_new();
	hi_phr_num_attr = pango_attr_list_new();
	pango_attr_list_ref(cand_num_attr);
	pango_attr_list_ref(phr_num_attr);
	pango_attr_list_ref(hi_cand_num_attr);
	pango_attr_list_ref(hi_phr_num_attr);

	attr = pango_attr_font_desc_new(fontdesc);
	attr->start_index = 0; attr->end_index = 2;
	pango_attr_list_insert(cand_num_attr, attr);
	pango_attr_list_insert(hi_cand_num_attr, attr);

	attr = pango_attr_font_desc_new(fontdesc);
	attr->start_index = 0; attr->end_index = 8;
	pango_attr_list_insert(phr_num_attr, attr);
	pango_attr_list_insert(hi_phr_num_attr, attr);
	
	attr = pango_attr_foreground_new(fg->red, fg->green, fg->blue);
	attr->start_index = 0; attr->end_index = 2;
	pango_attr_list_insert(cand_num_attr, attr);

	attr = pango_attr_foreground_new(0, 0x6000, 0);
	attr->start_index = 0; attr->end_index = G_MAXUINT;
	pango_attr_list_insert(phr_num_attr, attr);

	/* highlighted candidate color */
	attr = pango_attr_foreground_new(bg->red, bg->green, bg->blue);
	attr->start_index = 0; attr->end_index = G_MAXUINT;
	pango_attr_list_insert(hi_cand_num_attr, attr);
	attr = pango_attr_background_new(fg->red, fg->green, fg->blue);
	attr->start_index = 0; attr->end_index = G_MAXUINT;
	pango_attr_list_insert(hi_cand_num_attr, attr);

	/* highlighted phrase candidate color */
	attr = pango_attr_foreground_new(bg->red, bg->green, bg->blue);
	attr->start_index = 0; attr->end_index = G_MAXUINT;
	pango_attr_list_insert(hi_phr_num_attr, attr);
	attr = pango_attr_background_new(fg->red, fg->green, fg->blue);
	attr->start_index = 0; attr->end_index = G_MAXUINT;
	pango_attr_list_insert(hi_phr_num_attr, attr);
}

/**
 * To remove (actually rename) the _NET_WM_MOVERESIZE atom in the
 * _NET_SUPPORTED property. Since when it's present, e.g. when using
 * sawfish and metacity, the virtual keyboard cannot be moved (i.e.
 * the gtk_window_begin_move_drag function doesn't work). As the name
 * implies, this is a dirty hack.
 *
 * This function must be called right after gtk_init, since GTK+ will
 * cache the _NET_SUPPORTED property somewhere.
 */
static void
hack_NET_SUPPORTED(void)
{
	Atom *atoms = NULL;
	gulong n_atoms = 0;
	GdkAtom xproperty_ga;
	Atom xproperty = None;
	Atom tmp_xproperty = None;
	Atom type;
	gint format;
	//gulong nitems;
	gulong bytes_after;
	gulong i;
	Atom wmspec_supported_atom;

	/* don't create the atoms if they don't already exist! */
	gdk_error_trap_push();
	xproperty_ga = gdk_atom_intern("_NET_WM_MOVERESIZE", TRUE);
	if (xproperty_ga != GDK_NONE)
		xproperty = gdk_x11_atom_to_xatom(xproperty_ga);
	tmp_xproperty = gdk_x11_atom_to_xatom(gdk_atom_intern("_NET_WM_MOVERESIZE_T", FALSE));

	wmspec_supported_atom = //gdk_x11_get_xatom_by_name ("_NET_SUPPORTED");
				XInternAtom(gdk_display, "_NET_SUPPORTED", True);
        gdk_flush();
	if (gdk_error_trap_pop() != 0)
		return;

	/* these atoms don't always exist, eg. when no WM is running... */
	if (wmspec_supported_atom == None)
		return;
	if (xproperty == None)
		return;

        XGetWindowProperty (gdk_display, DefaultRootWindow(gdk_display),
                        wmspec_supported_atom , 0, G_MAXLONG,
                        False, XA_ATOM, &type, &format, &n_atoms,
                        &bytes_after, (guchar **)&atoms);

	for ( i = 0; i < n_atoms; i++ )
        {
                if (atoms[i] == xproperty)
                {
                        atoms[i] = tmp_xproperty;
			break;
                }
        }

	XChangeProperty(gdk_display, DefaultRootWindow(gdk_display),
			wmspec_supported_atom,
			type, format, PropModeReplace, (unsigned char*)atoms, n_atoms);
}

static void
init_attributes(GConfClient *client)
{
	gchar *fontstr;
	gchar *colorstr;
	gchar *bgstr;
	GdkColor color, bg;
	fontstr = gconf_client_get_string(client, NUMBER_FONT_KEY, NULL);
	colorstr = gconf_client_get_string(client, NUMBER_FG_KEY, NULL);
	bgstr = gconf_client_get_string(client, CANDIDATE_BG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &color))
		gdk_color_parse(NUMBER_FG, &color);
	if (!bgstr || !gdk_color_parse(bgstr, &bg))
		gdk_color_parse(CANDIDATE_BG, &bg);
	real_init_attributes(get_pango_fontdesc(fontstr, NUMBER_FONT), &color, &bg);
	g_free(colorstr);
	g_free(bgstr);
	g_free(fontstr);
}

static void
number_font_changed(GConfClient *client, 
		    guint cnxn_id,
		    GConfEntry *entry,
		    gpointer data)
{
	const gchar *fontstr = NULL;
	gchar *colorstr = NULL;
	gchar *bgstr = NULL;
	GdkColor color, bg;
	GConfValue *value = gconf_entry_get_value(entry);
	if (value != NULL)
		fontstr = gconf_value_get_string(value);

	colorstr = gconf_client_get_string(client, NUMBER_FG_KEY, NULL);
	bgstr = gconf_client_get_string(client, CANDIDATE_BG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &color))
		gdk_color_parse(NUMBER_FG, &color);
	if (!bgstr || !gdk_color_parse(bgstr, &bg))
		gdk_color_parse(CANDIDATE_BG, &bg);
	real_init_attributes(get_pango_fontdesc(fontstr, NUMBER_FONT), &color, &bg);
	g_free(colorstr);
}

static void
preedit_font_changed(GConfClient *client,
		     guint cnxn_id,
		     GConfEntry *entry,
		     gpointer data)
{
	const gchar *fontstr = NULL;
	GConfValue *value = gconf_entry_get_value(entry);
	if (value != NULL)
		fontstr = gconf_value_get_string(value);
	gtk_widget_modify_font(preedit->child, 
			       get_pango_fontdesc(fontstr, PREEDIT_FONT));
}

static void
preedit_realized(GtkWidget *w, gpointer data)
{
	GdkColormap *colormap = gtk_widget_get_colormap(w);
	gdk_colormap_alloc_color(colormap, &preedit->fg, TRUE, TRUE);
	//gdk_window_set_background(GTK_WINDOW(preedit->window)->frame, &preedit->fg);
	gdk_window_set_back_pixmap(preedit->window->window, NULL, FALSE);
}

static gboolean
preedit_configure(GtkWidget *w, GdkEventConfigure *event, gpointer data)
{
	preedit->x = event->x;
	preedit->y = event->y;
	preedit->width = event->width;
	preedit->height = event->height;
	if (lu_vert->poppedup) {
		reposition_lu_vert(event->x, event->y);
	} else if (lu_horz->poppedup) {
		reposition_lu_horz(event->x, event->y);
	}
	return FALSE;
}

static void
init_preedit_window(GConfClient *client)
{
	DEBUG_LOG("xaux : init preedit window\n");

	gchar *fontstr = NULL;
	gchar *colorstr = NULL;
	GdkColor color;

	gtk_rc_parse_string("style 'unihan-compose-window-style' "
			    "{"
			    "bg_pixmap[NORMAL] = '<none>'"
			    "}"
			    "widget 'unihan-compose-window' style 'unihan-compose-window-style'");

	preedit = g_new0(WindowData, 1);
	preedit->window = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_set_name(preedit->window, "unihan-compose-window");
	gtk_container_set_border_width(GTK_CONTAINER(preedit->window), 1);
	g_signal_connect_after(G_OBJECT(preedit->window), "realize",
			       G_CALLBACK(preedit_realized), NULL);
	g_signal_connect(G_OBJECT(preedit->window), "configure-event",
			       G_CALLBACK(preedit_configure), NULL);
	
        gtk_window_set_keep_above(GTK_WINDOW(preedit->window), TRUE);
        gtk_window_set_decorated(GTK_WINDOW(preedit->window), FALSE);
        gtk_window_set_type_hint(GTK_WINDOW(preedit->window), GDK_WINDOW_TYPE_HINT_MENU);
        gtk_window_set_accept_focus(GTK_WINDOW(preedit->window), FALSE);

	preedit->child = im_label_new(NULL);
	fontstr = gconf_client_get_string(client, PREEDIT_FONT_KEY, NULL);
	gtk_widget_modify_font(preedit->child, 
			       get_pango_fontdesc(fontstr, PREEDIT_FONT));
	g_free(fontstr);
	gtk_misc_set_alignment(GTK_MISC(preedit->child), 0.0, 0.5);
	gtk_misc_set_padding(GTK_MISC(preedit->child), 4, 2);

	preedit->ebox = gtk_event_box_new();
	gtk_container_add(GTK_CONTAINER(preedit->ebox), preedit->child);
	gtk_widget_show_all(preedit->ebox);
	gtk_container_add(GTK_CONTAINER(preedit->window), preedit->ebox);

	preedit->poppedup = FALSE;

	colorstr = gconf_client_get_string(client, PREEDIT_FG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &preedit->fg)) {
		gdk_color_parse(PREEDIT_FG, &preedit->fg);
	}
	g_free(colorstr);
	colorstr = gconf_client_get_string(client, PREEDIT_BG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &preedit->bg)) {
		gdk_color_parse(PREEDIT_BG, &preedit->bg);
	}
	g_free(colorstr);
	gdk_color_parse(PREEDIT_FG, &preedit->fg);
	gdk_color_parse(PREEDIT_BG, &preedit->bg);
	gtk_widget_modify_fg(preedit->ebox, GTK_STATE_NORMAL, &preedit->fg);
	gtk_widget_modify_fg(preedit->child, GTK_STATE_NORMAL, &preedit->fg);
	gtk_widget_modify_bg(preedit->ebox, GTK_STATE_NORMAL, &preedit->bg);
	gtk_widget_modify_base(preedit->ebox, GTK_STATE_NORMAL, &preedit->bg);
	gdk_color_parse("#000000", &color);
	gtk_widget_modify_bg(preedit->window, GTK_STATE_NORMAL, &color);
}

static void
status_font_changed(GConfClient *client,
		    guint cnxn_id,
		    GConfEntry *entry,
		    gpointer data)
{
	const gchar *fontstr = NULL;
	PangoFontDescription *fontdesc = NULL;
	GConfValue *value = gconf_entry_get_value(entry);

	if (value != NULL) 
		fontstr = gconf_value_get_string(value);
	fontdesc = get_pango_fontdesc(fontstr, STATUS_FONT);
	gtk_widget_modify_font(lu_horz_status_label, fontdesc);
	gtk_widget_modify_font(lu_vert_status_label, fontdesc);
}

#include "images/left.xbm"
#include "images/right.xbm"
static void
lu_horz_realized(GtkWidget *w, gpointer data)
{
	GdkPixmap *arrow;
	gchar *colorstr;
	GdkColormap *colormap;
	GdkColor fg, bg;
	GConfClient *client = gconf_client_get_default();

	colorstr = gconf_client_get_string(client, STATUS_FG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &fg)) {
		gdk_color_parse(STATUS_FG, &fg);
	}
	g_free(colorstr);
	colorstr = gconf_client_get_string(client, CANDIDATE_BG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &bg)) {
		gdk_color_parse(CANDIDATE_BG, &bg);
	}
	g_free(colorstr);
	colormap = gtk_widget_get_colormap(w);
	gdk_colormap_alloc_color(colormap, &fg, TRUE, TRUE);
	gdk_colormap_alloc_color(colormap, &bg, TRUE, TRUE);
	gdk_colormap_alloc_color(colormap, &lu_horz->fg, TRUE, TRUE);
	
	arrow = gdk_pixmap_create_from_data(w->window, left_bits, 
			left_width, left_height, -1, &fg, &bg);
	gtk_image_set_from_pixmap(GTK_IMAGE(left_arrow), arrow, NULL);
	arrow = gdk_pixmap_create_from_data(w->window, right_bits, 
			right_width, right_height, -1, &fg, &bg);
	gtk_image_set_from_pixmap(GTK_IMAGE(right_arrow), arrow, NULL);
	
	//gdk_window_set_background(GTK_WINDOW(lu_horz->window)->frame, &lu_horz->fg);
	gdk_window_set_back_pixmap(lu_horz->window->window, NULL, FALSE);
}

static void
init_lu_horz_window(GConfClient *client)
{
	GtkWidget *bigbox, *box;
	//GdkPixmap *arrow;
	//GdkColormap *colormap;
	gchar *fontstr, *colorstr;
	GdkColor color;

	gtk_rc_parse_string("style 'unihan-candidate-h-window-style' "
			    "{"
			    "bg_pixmap[NORMAL] = '<none>'"
			    "}"
			    "widget 'unihan-candidate-h-window' style 'unihan-candidate-h-window-style'");

	lu_horz = g_new0(WindowData, 1);
	lu_horz->window = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_set_name(lu_horz->window, "unihan-candidate-h-window");
	gtk_container_set_border_width(GTK_CONTAINER(lu_horz->window), 1);
	//gtk_window_set_has_frame(GTK_WINDOW(lu_horz->window), TRUE);
	//gtk_window_set_frame_dimensions(GTK_WINDOW(lu_horz->window), 1, 1, 1, 1);

	bigbox = gtk_vbox_new(FALSE, 4);
	gtk_container_set_border_width(GTK_CONTAINER(bigbox), 0);
	
	box = gtk_hbox_new(FALSE, 8);
	gtk_container_set_border_width(GTK_CONTAINER(box), 2);
	lu_horz->child = box;

	bar_horz = gtk_hbox_new(FALSE, 4);
	lu_horz_status_label = gtk_label_new(NULL);

	fontstr = gconf_client_get_string(client, STATUS_FONT_KEY, NULL);
	gtk_widget_modify_font(lu_horz_status_label, 
			       get_pango_fontdesc(fontstr, STATUS_FONT));
	
	left_arrow = gtk_image_new();
	right_arrow = gtk_image_new();
	g_signal_connect_after(G_OBJECT(lu_horz->window), "realize",
			       G_CALLBACK(lu_horz_realized), NULL);
	gtk_misc_set_alignment(GTK_MISC(left_arrow), 0.5, 0.5);
	gtk_misc_set_alignment(GTK_MISC(right_arrow), 0.5, 0.5);
	gtk_misc_set_alignment(GTK_MISC(lu_horz_status_label), 0.0, 0.5);
	gtk_box_pack_start(GTK_BOX(bar_horz), lu_horz_status_label, TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(bar_horz), left_arrow, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(bar_horz), right_arrow, FALSE, FALSE, 0);
	
	gtk_box_pack_start_defaults(GTK_BOX(bigbox), box);
	gtk_box_pack_start_defaults(GTK_BOX(bigbox), bar_horz);

	lu_horz->ebox = gtk_event_box_new();
	gtk_container_add(GTK_CONTAINER(lu_horz->ebox), bigbox);
	gtk_widget_show_all(lu_horz->ebox);
	gtk_container_add(GTK_CONTAINER(lu_horz->window), lu_horz->ebox);

	lu_horz->poppedup = FALSE;

	colorstr = gconf_client_get_string(client, CANDIDATE_FG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &lu_horz->fg)) {
		gdk_color_parse(CANDIDATE_FG, &lu_horz->fg);
	}
	g_free(colorstr);
	colorstr = gconf_client_get_string(client, CANDIDATE_BG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &lu_horz->bg)) {
		gdk_color_parse(CANDIDATE_BG, &lu_horz->bg);
	}
	g_free(colorstr);
	gtk_widget_modify_bg(lu_horz->ebox, GTK_STATE_NORMAL, &lu_horz->bg);
	gtk_widget_modify_base(lu_horz->ebox, GTK_STATE_NORMAL, &lu_horz->bg);
	gtk_widget_modify_bg(lu_horz->window, GTK_STATE_NORMAL, &lu_horz->fg);
	colorstr = gconf_client_get_string(client, STATUS_FG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &color)) {
		gdk_color_parse(STATUS_FG, &color);
	}
	g_free(colorstr);
	gtk_widget_modify_fg(lu_horz_status_label, GTK_STATE_NORMAL, &color);
}

#include "images/up.xbm"
#include "images/down.xbm"
static void
lu_vert_realized(GtkWidget *w, gpointer data)
{
	GdkPixmap *arrow;
	//GdkBitmap *arrow_mask;
	gchar *colorstr;
	GdkColormap *colormap;
	GdkColor fg, bg;
	GConfClient *client = gconf_client_get_default();

	colorstr = gconf_client_get_string(client, STATUS_FG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &fg)) {
		gdk_color_parse(STATUS_FG, &fg);
	}
	g_free(colorstr);
	colorstr = gconf_client_get_string(client, CANDIDATE_BG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &bg)) {
		gdk_color_parse(CANDIDATE_BG, &bg);
	}
	g_free(colorstr);
	colormap = gtk_widget_get_colormap(w);
	gdk_colormap_alloc_color(colormap, &fg, TRUE, TRUE);
	gdk_colormap_alloc_color(colormap, &bg, TRUE, TRUE);
	gdk_colormap_alloc_color(colormap, &lu_vert->fg, TRUE, TRUE);
	
	arrow = gdk_pixmap_create_from_data(w->window, up_bits, 
			up_width, up_height, -1, &fg, &bg);
    GtkWidget *up_arrow_img = gtk_image_new();
	gtk_image_set_from_pixmap(GTK_IMAGE(up_arrow_img), arrow, NULL);
	arrow = gdk_pixmap_create_from_data(w->window, down_bits, 
			down_width, down_height, -1, &fg, &bg);
    GtkWidget *down_arrow_img = gtk_image_new();
	gtk_image_set_from_pixmap(GTK_IMAGE(down_arrow_img), arrow, NULL);
    gtk_container_add(GTK_CONTAINER(up_arrow), up_arrow_img);
    gtk_container_add(GTK_CONTAINER(down_arrow), down_arrow_img);
    gtk_widget_show(up_arrow_img);
    gtk_widget_show(down_arrow_img);

	//gdk_window_set_background(GTK_WINDOW(lu_vert->window)->frame, &lu_vert->fg);
	gdk_window_set_back_pixmap(lu_vert->window->window, NULL, FALSE);

        //gdouble alpha = gconf_client_get_float(client, CANDIDATE_ALPHA_KEY, NULL);
        //set_opacity_prop(lu_vert->window->window, alpha * 0xffffffff);
}

static void
init_lu_vert_window(GConfClient *client)
{
	GtkWidget *box;
	PangoFontDescription *fontdesc;
	gchar *fontstr, *colorstr;
	GdkColor color;

	gtk_rc_parse_string("style 'unihan-candidate-v-window-style' "
			    "{"
			    "bg_pixmap[NORMAL] = '<none>'"
			    "}"
			    "widget 'unihan-candidate-v-window' style 'unihan-candidate-v-window-style'");

	lu_vert = g_new0(WindowData, 1);
	lu_vert->window = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_set_name(lu_vert->window, "unihan-candidate-v-window");
	gtk_container_set_border_width(GTK_CONTAINER(lu_vert->window), 1);
        gtk_window_set_keep_above(GTK_WINDOW(lu_vert->window), TRUE);
        gtk_window_set_decorated(GTK_WINDOW(lu_vert->window), FALSE);
        gtk_window_set_type_hint(GTK_WINDOW(lu_vert->window), GDK_WINDOW_TYPE_HINT_MENU);

	box = gtk_vbox_new(FALSE, 4);
	gtk_container_set_border_width(GTK_CONTAINER(box), 2);
	
	bar_vert = gtk_hbox_new(FALSE, 0);
	phrase_hint = gtk_label_new(NULL);
	fontdesc = pango_font_description_from_string(PREEDIT_FONT);
	gtk_widget_modify_font(phrase_hint, fontdesc);

	up_arrow = gtk_flat_button_new();
	g_signal_connect(G_OBJECT(up_arrow), "clicked", G_CALLBACK(candidate_page_pressed_cb), GINT_TO_POINTER(-1));
	down_arrow = gtk_flat_button_new();
	g_signal_connect(G_OBJECT(down_arrow), "clicked", G_CALLBACK(candidate_page_pressed_cb), GINT_TO_POINTER(1));
	g_signal_connect_after(G_OBJECT(lu_vert->window), "realize",
			       G_CALLBACK(lu_vert_realized), NULL);
	
	lu_vert_status_label = gtk_label_new(NULL);
	fontstr = gconf_client_get_string(client, STATUS_FONT_KEY, NULL);
	gtk_widget_modify_font(lu_vert_status_label, 
			       get_pango_fontdesc(fontstr, STATUS_FONT));
	
	gtk_flat_button_set_alignment(GTK_FLAT_BUTTON(up_arrow), 0.5, 0.5);
	gtk_flat_button_set_alignment(GTK_FLAT_BUTTON(down_arrow), 0.5, 0.5);
	gtk_misc_set_alignment(GTK_MISC(lu_vert_status_label), 0.0, 1.0);

	gtk_box_pack_start(GTK_BOX(bar_vert), lu_vert_status_label, TRUE, TRUE, 0);
	gtk_box_pack_start(GTK_BOX(bar_vert), up_arrow, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(bar_vert), down_arrow, FALSE, FALSE, 0);
	gtk_box_pack_end(GTK_BOX(box), bar_vert, FALSE, FALSE, 0);

	GtkWidget *candbox = gtk_vbox_new(FALSE, 0);
	gtk_box_set_homogeneous(GTK_BOX(candbox), TRUE);
	gtk_box_pack_start(GTK_BOX(box), candbox, FALSE, FALSE, 0);
	lu_vert->child = candbox;

	GtkWidget *space = gtk_alignment_new(0.5, 1.0, 1.0, 0.0);
	gtk_box_pack_end_defaults(GTK_BOX(box), space);

	lu_vert->ebox = gtk_event_box_new();
	gtk_container_add(GTK_CONTAINER(lu_vert->ebox), box);
	gtk_widget_show_all(lu_vert->ebox);
	gtk_container_add(GTK_CONTAINER(lu_vert->window), lu_vert->ebox);

	lu_vert->poppedup = FALSE;
	lu_vert->gravity = GRAVITY_TOP;

	colorstr = gconf_client_get_string(client, CANDIDATE_FG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &lu_vert->fg)) {
		gdk_color_parse(CANDIDATE_FG, &lu_vert->fg);
	}
	g_free(colorstr);
	colorstr = gconf_client_get_string(client, CANDIDATE_BG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &lu_vert->bg)) {
		gdk_color_parse(CANDIDATE_BG, &lu_vert->bg);
	}
	g_free(colorstr);
	gtk_widget_modify_bg(lu_vert->ebox, GTK_STATE_NORMAL, &lu_vert->bg);
	gtk_widget_modify_base(lu_vert->ebox, GTK_STATE_NORMAL, &lu_vert->bg);
	gtk_widget_modify_bg(lu_vert->window, GTK_STATE_NORMAL, &lu_vert->fg);
	colorstr = gconf_client_get_string(client, STATUS_FG_KEY, NULL);
	if (!colorstr || !gdk_color_parse(colorstr, &color)) {
		gdk_color_parse(STATUS_FG, &color);
	}
	g_free(colorstr);
	gtk_widget_modify_fg(lu_vert_status_label, GTK_STATE_NORMAL, &color);
}


static gint
tooltip_timeout(gpointer data)
{
	TooltipData *tip = (TooltipData *)data;

	GDK_THREADS_ENTER();

	gtk_widget_hide(tip->window);
	tip->timer_tag = 0;

	GDK_THREADS_LEAVE();

	return FALSE;
}


static void
hide_tooltip(void)
{
	if (tooltip->timer_tag > 0) {
		g_source_remove(tooltip->timer_tag);
		tooltip->timer_tag = 0;
	}
	gtk_widget_hide(tooltip->window);
}


static void
draw_tooltip(int x, int y, char *string)
{
	if (tooltip->timer_tag > 0) {
		g_source_remove(tooltip->timer_tag);
		tooltip->timer_tag = 0;
	}

	gtk_widget_ensure_style(tooltip->window);
	gtk_label_set_text(GTK_LABEL(tooltip->label), string);

	GtkRequisition req;
	gtk_widget_size_request(tooltip->window, &req);

	int screen_w = gdk_screen_width();
	int screen_h = gdk_screen_height();

	if (x + req.width >= screen_w) {
		x = screen_w - req.width;
		if (x < 0) x = 0;
	}

	if (y + req.height >= screen_h) {
		y -= (PREEDIT_HEIGHT + 4 + req.height);
	}

	int timeout = 500; // 1000 = 1 second
	tooltip->timer_tag = g_timeout_add(timeout, tooltip_timeout, (gpointer)tooltip);

	gtk_window_move(GTK_WINDOW(tooltip->window), x, y);
	gtk_widget_show(tooltip->window);
}


static gint
tooltip_paint_window(GtkTooltips *tooltips)
{
	GtkRequisition req;

	gtk_widget_size_request(tooltip->window, &req);
	gtk_paint_flat_box(tooltip->window->style, tooltip->window->window,
	                   GTK_STATE_NORMAL, GTK_SHADOW_OUT, 
	                   NULL, GTK_WIDGET(tooltip->window), "tooltip",
	                   0, 0, req.width, req.height);

	return FALSE;
}


static void
init_tooltip_window(void)
{
	tooltip = g_new0(TooltipData, 1);
	tooltip->window = gtk_window_new(GTK_WINDOW_POPUP);
	gtk_widget_set_name(tooltip->window, "gtk-tooltips"); // inherit gtk tooltips theming
	gtk_window_set_type_hint(GTK_WINDOW(tooltip->window), GDK_WINDOW_TYPE_HINT_MENU);
	gtk_widget_set_app_paintable(tooltip->window, TRUE);
	gtk_window_set_resizable(GTK_WINDOW(tooltip->window), FALSE);
	gtk_container_set_border_width(GTK_CONTAINER(tooltip->window), 4);

	g_signal_connect_swapped(tooltip->window,
				 "expose_event",
				 G_CALLBACK(tooltip_paint_window), tooltip);
	
	tooltip->label = gtk_label_new(NULL);
	gtk_label_set_line_wrap(GTK_LABEL(tooltip->label), TRUE);
	gtk_misc_set_alignment(GTK_MISC(tooltip->label), 0.5, 0.5);
	gtk_widget_show(tooltip->label);

	gtk_container_add(GTK_CONTAINER(tooltip->window), tooltip->label);

	g_signal_connect(tooltip->window,
			 "destroy",
			 G_CALLBACK(gtk_widget_destroyed),
			 &tooltip->window);
}


int
main(int argc, char *argv[])
{
	DEBUG_LOG("xaux : staring xaux\n");

	GError *err = NULL;
	GConfClient *client;

	/* We will always use a UTF-8 locale to prevent stuffup */
	gtk_set_locale();

	/* Initialize the i18n stuff */
	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
	bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
	textdomain(GETTEXT_PACKAGE);

	gtk_init(&argc, &argv);
	if (!gconf_init(argc, argv, &err)) {
		fprintf(stderr, "Failed to init GConf: %s", err->message);
		g_error_free(err);
		exit(EXIT_FAILURE);
	}

	hack_NET_SUPPORTED();

	client = gconf_client_get_default();

	init_attributes(client);
	init_lookup_data(client);
	init_preedit_window(client);
	init_lu_horz_window(client);
	init_lu_vert_window(client);
	init_tooltip_window();

	/* Register gconf callbacks */
	gconf_client_add_dir(client, UNIHAN_ROOT_KEY, 
			     GCONF_CLIENT_PRELOAD_NONE, NULL);
	gconf_client_notify_add(client, NUMBER_FONT_KEY, 
			        number_font_changed,
				NULL, NULL, NULL);
	gconf_client_notify_add(client, CANDIDATE_FONT_KEY, 
			        candidate_font_changed,
				NULL, NULL, NULL);
	gconf_client_notify_add(client, PREEDIT_FONT_KEY, 
			        preedit_font_changed,
				NULL, NULL, NULL);
	gconf_client_notify_add(client, STATUS_FONT_KEY, 
			        status_font_changed,
				NULL, NULL, NULL);
	gconf_client_notify_add(client, DEFAULT_IM_KEY,
				default_im_changed,
				NULL, NULL, NULL);
	gconf_client_notify_add(client, TC_SC_KEY,
				tc_sc_changed,
				NULL, NULL, NULL);
	gconf_client_notify_add(client, PREEDIT_FG_KEY,
				preedit_color_changed,
				GINT_TO_POINTER(1), NULL, NULL);
	gconf_client_notify_add(client, PREEDIT_BG_KEY,
				preedit_color_changed,
				GINT_TO_POINTER(0), NULL, NULL);
	gconf_client_notify_add(client, CANDIDATE_FG_KEY,
				candidate_color_changed,
				GINT_TO_POINTER(1), NULL, NULL);
	gconf_client_notify_add(client, CANDIDATE_BG_KEY,
				candidate_color_changed,
				GINT_TO_POINTER(0), NULL, NULL);
        /*gconf_client_notify_add(client, CANDIDATE_ALPHA_KEY,
                                candidate_alpha_changed,
                                NULL, NULL, NULL);*/
	gconf_client_notify_add(client, NUMBER_FG_KEY,
				number_color_changed,
				GINT_TO_POINTER(1), NULL, NULL);
	gconf_client_notify_add(client, NUMBER_BG_KEY,
				number_color_changed,
				GINT_TO_POINTER(0), NULL, NULL);
	gconf_client_notify_add(client, STATUS_FG_KEY,
				status_color_changed,
				GINT_TO_POINTER(1), NULL, NULL);
	gconf_client_notify_add(client, STATUS_BG_KEY,
				status_color_changed,
				GINT_TO_POINTER(0), NULL, NULL);
//	gconf_client_add_dir(client, UNIHAN_ROOT_KEY "/im-options", 
//			     GCONF_CLIENT_PRELOAD_NONE, NULL);
        gconf_client_notify_add(client, UNIHAN_ROOT_KEY "/im-options",
                                im_option_changed,
                                NULL, NULL, NULL);
	gconf_client_notify_add(client, CANDIDATE_HORIZONTALLY_KEY,
							candidate_direction_changed,
							NULL, NULL, NULL);
    gconf_client_notify_add(client, PHRASE_MODE_KEY,
                            phrase_mode_changed,
                            NULL, NULL, NULL);
    gconf_client_notify_add(client, USE_GOOGLE_SUGGEST_KEY,
                            phrase_mode_changed,
                            NULL, NULL, NULL);
    gconf_client_notify_add(client, USE_PROXY_KEY, proxy_changed, NULL, NULL, NULL);
    gconf_client_notify_add(client, PROXY_HOST_KEY, proxy_changed, NULL, NULL, NULL);
    gconf_client_notify_add(client, PROXY_PORT_KEY, proxy_changed, NULL, NULL, NULL);
    gconf_client_notify_add(client, USE_PROXY_AUTH, proxy_changed, NULL, NULL, NULL);
    gconf_client_notify_add(client, PROXY_USER, proxy_changed, NULL, NULL, NULL);
    gconf_client_notify_add(client, PROXY_PASSWORD, proxy_changed, NULL, NULL, NULL);

	_candidate_horizontally = gconf_client_get_bool(client, CANDIDATE_HORIZONTALLY_KEY, NULL);

    _soupsession = soup_session_async_new();
    set_proxy(_soupsession);
    update_use_google_suggest();

	/* Register event handler */
	gdk_window_add_filter(preedit->window->window, xaux_ext_event_handler, 0);

	/* Register aux class */
	gtk_widget_realize(preedit->window);
	display = GDK_WINDOW_XDISPLAY(preedit->window->window);
	if (xaux_ext_init_classes(display, 
			&xaux_class,
			GDK_WINDOW_XWINDOW(preedit->window->window)) == False) {
		return 2;
	}

	gtk_main();

	return 0;
}

void
send_to_engine(int int_count, int *ints, int str_count, char **strs)
{
	int i;
	aux_ext_data_t *aux_data;

	if (im_ == VOID_IM_ID || ic_ == VOID_IC_ID || !xc_)
		return;

	if (!int_count && !str_count)
		return;

	aux_data = g_new0(aux_ext_data_t, 1);

	aux_data->im = im_;
	aux_data->ic = ic_;
	aux_data->integer_count = int_count;
	aux_data->string_count = str_count;

	if (aux_data->integer_count) {
		aux_data->integer_list = g_new0(int, aux_data->integer_count);
		for (i = 0; i < aux_data->integer_count; i++) {
			aux_data->integer_list[i] = ints[i];
		}
	}

	if (aux_data->string_count) {
		aux_data->string_list = 
			g_new0(aux_ext_string_t, aux_data->string_count);
		for (i = 0; i < aux_data->string_count; i++) {
			aux_ext_string_t *p = &aux_data->string_list[i];
			p->length = strlen(strs[i]);
			p->ptr = (unsigned char *)strdup(strs[i]);
		}
	}

	gdk_error_trap_push();
	xaux_ext_SetValue(gdk_display, xc_, aux_data);
	XFlush(gdk_display);
	if (gdk_error_trap_pop() != 0) {
		// hey! BadWindow?
		im_ = VOID_IM_ID;
		ic_ = VOID_IC_ID;
		xc_ = NULL;
	}

	if (aux_data->integer_list)
		g_free(aux_data->integer_list);
	if (aux_data->string_list) {
		for (i = 0; i < aux_data->string_count; i++) {
			aux_ext_string_t *p = &aux_data->string_list[i];
			if (p && p->ptr)
				g_free(p->ptr);
		}
		g_free(aux_data->string_list);
	}
	g_free(aux_data);
}

static void
propagate_default_im_change(const gchar *im)
{
	int int_values[1] = { UNIHAN_ENGINE_SET_DEFAULT_IM };
	char *str_values[1] = { (char*)im };

	send_to_engine(1, int_values, 1, str_values);
}

static void
default_im_changed(GConfClient *client,
		   guint cnxn_id,
		   GConfEntry *entry,
		   gpointer data)
{
	const gchar *default_im = NULL;
	GConfValue *value = gconf_entry_get_value(entry);
	if (value != NULL)
		default_im = gconf_value_get_string(value);
	if (default_im != NULL)
		propagate_default_im_change(default_im);
}

static void
propagate_tc_sc_change(const int tc_sc_mode)
{
	int int_values[2] = { UNIHAN_ENGINE_SET_TCSC_MODE, tc_sc_mode };
	send_to_engine(2, int_values, 0, NULL);
}

static void
tc_sc_changed(GConfClient *client,
	      guint cnxn_id,
	      GConfEntry *entry,
	      gpointer data)
{
	int tc_sc_mode = 0;
	GConfValue *value = gconf_entry_get_value(entry);
	if (value != NULL) {
		tc_sc_mode = gconf_value_get_int(value);
		//propagate_tc_sc_change(tc_sc_mode);
	}
}

static void
propagate_phrase_mode_change(const gboolean phrase_mode)
{
	int int_values[2] = { UNIHAN_ENGINE_SET_PHRASE_MODE, phrase_mode ? 1 : 0 };
	send_to_engine(2, int_values, 0, NULL);
}

static void
preedit_color_changed(GConfClient *client,
		      guint cnxn_id,
		      GConfEntry *entry,
		      gpointer data)
{
	gint is_fg = GPOINTER_TO_INT(data);
	const gchar *colorstr = NULL;
	GdkColor *color = (is_fg) ? &preedit->fg : &preedit->bg;
	GConfValue *value = gconf_entry_get_value(entry);
	
	if (value != NULL)
		colorstr = gconf_value_get_string(value);
	if (colorstr == NULL || !gdk_color_parse(colorstr, color))
		return;

	if (is_fg) {
		gtk_widget_modify_fg(preedit->ebox, GTK_STATE_NORMAL, color);
		gtk_widget_modify_fg(preedit->child, GTK_STATE_NORMAL, color);
	} else {
		gtk_widget_modify_bg(preedit->ebox, GTK_STATE_NORMAL, color);
		gtk_widget_modify_base(preedit->ebox, GTK_STATE_NORMAL, color);
	}
}

static void
number_color_changed(GConfClient *client,
		     guint cnxn_id,
		     GConfEntry *entry,
		     gpointer data)
{
	gint is_fg = GPOINTER_TO_INT(data);
	const gchar *colorstr = NULL;
	gchar *fontstr = NULL, *bgstr = NULL;
	GdkColor color, bg;
	GConfValue *value = gconf_entry_get_value(entry);
	
	if (!is_fg)	// No support for background yet
		return;
	if (value != NULL)
		colorstr = gconf_value_get_string(value);
	if (colorstr == NULL || !gdk_color_parse(colorstr, &color))
		return;

	bgstr = gconf_client_get_string(client, CANDIDATE_BG_KEY, NULL);
	if (!bgstr || !gdk_color_parse(bgstr, &bg))
		gdk_color_parse(CANDIDATE_BG, &bg);

	fontstr = gconf_client_get_string(client, NUMBER_FONT_KEY, NULL);
	real_init_attributes(get_pango_fontdesc(fontstr, NUMBER_FONT), &color, &bg);
	g_free(fontstr);
}

static void
status_color_changed(GConfClient *client,
		     guint cnxn_id,
		     GConfEntry *entry,
		     gpointer data)
{
	gint is_fg = GPOINTER_TO_INT(data);
	const gchar *colorstr = NULL;
	GdkColor color;
	GConfValue *value = gconf_entry_get_value(entry);
	
	if (!is_fg)	// No support for background yet
		return;
	if (value != NULL)
		colorstr = gconf_value_get_string(value);
	if (colorstr == NULL || !gdk_color_parse(colorstr, &color))
		return;

	gtk_widget_modify_fg(lu_horz_status_label, GTK_STATE_NORMAL, &color);
	gtk_widget_modify_fg(lu_vert_status_label, GTK_STATE_NORMAL, &color);
}

static void
candidate_color_changed(GConfClient *client,
		        guint cnxn_id,
		        GConfEntry *entry,
		        gpointer data)
{
	gint is_fg = GPOINTER_TO_INT(data);
	const gchar *colorstr = NULL;
	GdkColor *color = (is_fg) ? &lu_vert->fg : &lu_vert->bg;
	GConfValue *value = gconf_entry_get_value(entry);
	LookupData *d = lookup_data;
	int i;
	
	if (value != NULL)
		colorstr = gconf_value_get_string(value);
	if (colorstr == NULL || !gdk_color_parse(colorstr, color))
		return;

	if (!is_fg) {
		gdk_color_parse(colorstr, &lu_horz->bg);
		if (_candidate_horizontally) {
			gtk_widget_modify_bg(lu_horz->ebox, GTK_STATE_NORMAL, color);
			gtk_widget_modify_base(lu_horz->ebox, GTK_STATE_NORMAL, color);
		} else {
			gtk_widget_modify_bg(lu_vert->ebox, GTK_STATE_NORMAL, color);
			gtk_widget_modify_base(lu_vert->ebox, GTK_STATE_NORMAL, color);
		}
		return;
	}
	gdk_color_parse(colorstr, &lu_horz->fg);
	for (i = 0; i < CANDS_MAX; i++) {
		gtk_widget_modify_fg(d->cand[i], GTK_STATE_NORMAL, color);
	}
	if (_candidate_horizontally) {
		gtk_widget_modify_bg(lu_horz->window, GTK_STATE_NORMAL, color);
		gtk_widget_modify_base(lu_horz->window, GTK_STATE_NORMAL, color);
	} else {
		gtk_widget_modify_bg(lu_vert->window, GTK_STATE_NORMAL, color);
		gtk_widget_modify_base(lu_vert->window, GTK_STATE_NORMAL, color);
	}
}

#if 0
static void
candidate_alpha_changed(GConfClient *client,
                        guint cnxn_id,
                        GConfEntry *entry,
                        gpointer data)
{
        GConfValue *value = gconf_entry_get_value(entry);
        double alpha;

        if (value == NULL)
                return;

        alpha = gconf_value_get_float(value);
        DEBUG_LOG("alpha: %f", alpha);
        set_opacity_prop(lu_vert->window->window, alpha * 0xffffffff);
}
#endif

static void
im_option_changed(GConfClient *client,
		  guint cnxn_id,
		  GConfEntry *entry,
		  gpointer data)
{
	GConfValue *value = gconf_entry_get_value(entry);
        const gchar *entryKey = gconf_entry_get_key(entry);
        gchar *optIM = NULL;
        gchar *optKey = NULL;
        gchar *optVal = NULL;

	if (value == NULL || value->type == GCONF_VALUE_INVALID) 
                return;

        optVal = gconf_value_to_string(value);

        optIM = g_strdup(entryKey + strlen(UNIHAN_ROOT_KEY "/im-options/"));
        optKey = index(optIM, '/');
        if (optKey == NULL) {
                g_free(optIM);
                g_free(optVal);
                return;
        }
        optKey++;
        *(optKey - 1) = '\0';

        DEBUG_LOG("IM option changed: %s %s = %s\n", optIM, optKey, optVal);

        propagate_im_option_change(optIM, optKey, optVal);
 
        g_free(optIM);
        g_free(optVal);
}


static void
propagate_im_option_change(const gchar *im, const gchar *key, const gchar *value)
{
	int int_values[1] = { UNIHAN_ENGINE_IM_CONFIG_CHANGE };
	char *str_values[3] = { (char*)im, (char*)key, (char*)value };

	send_to_engine(1, int_values, 3, str_values);
}

static void
candidate_direction_changed(GConfClient *client,
		  guint cnxn_id,
		  GConfEntry *entry,
		  gpointer data)
{
	GConfValue *value = gconf_entry_get_value(entry);
	_candidate_horizontally = gconf_value_get_bool(value);
}

static gboolean
candidate_button_pressed_cb(GtkWidget *w, gpointer data)
{
    gint cand_num = GPOINTER_TO_INT(data);
    int int_values[2];
    int_values[0] = UNIHAN_ENGINE_CAND_COMMIT;
    int_values[1] = cand_num;
    send_to_engine(2, int_values, 0, NULL);
    return TRUE;
}

static gboolean
candidate_page_pressed_cb(GtkWidget *w, gpointer data)
{
    gint dir = GPOINTER_TO_INT(data);
    int int_values[1];
    int_values[0] = (dir > 0)? UNIHAN_ENGINE_CAND_PAGEDOWN : UNIHAN_ENGINE_CAND_PAGEUP;
    send_to_engine(1, int_values, 0, NULL);
    return TRUE;
}

static LookupData *
google_suggest_lookup_data_init(void)
{
    LookupData *d = g_new0(LookupData, 1);

    return d;
}

static void
google_suggest_get(char *query)
{
    SoupMessage *message;
    char *uri;

    uri = g_strdup_printf("http://suggestqueries.google.com/complete/search?output=firefox&qu=%s", query);
    message = soup_message_new(SOUP_METHOD_GET, uri);
    soup_message_add_header(message->request_headers, "User-Agent", "Unihan/" VERSION);
    soup_session_queue_message(_soupsession, message, google_suggest_received, NULL);

    g_free(uri);
}

static void
google_suggest_received(SoupMessage *message, gpointer user_data)
{
    if (!SOUP_STATUS_IS_SUCCESSFUL(message->status_code)) {
        // FIXME
        return;
    }

    struct json_tokener *tok = json_tokener_new();
    struct json_object *resp = json_tokener_parse(message->response.body);
    if (json_object_get_type(resp) != json_type_array 
            || json_object_array_length(resp) != 2) {
        DEBUG_LOG("%s: response not in expected format (should be a 2 element array)\n", __func__);
        return;
    }

    struct json_object *key = json_object_array_get_idx(resp, 0);
    struct json_object *results = json_object_array_get_idx(resp, 1);
    int num_cands = json_object_array_length(results);

    if (num_cands) {
        int int_values[3] = { UNIHAN_ENGINE_EXTRA_CANDS, 1, num_cands };
        char *str_values[num_cands + 1];
        str_values[0] = json_object_get_string(key);
        int key_len = strlen(str_values[0]);

        int i;
        for (i = 0; i < num_cands; ++i) {
            struct json_object *obj = json_object_array_get_idx(results, i);

            /* trim off the key from the results */
            str_values[i + 1] = json_object_get_string(obj) + key_len;
        }

        send_to_engine(3, int_values, num_cands + 1, str_values);
    }

    json_object_put(resp);
    json_tokener_free(tok);
}

static void
update_use_google_suggest(void)
{
    GConfClient *client = gconf_client_get_default();
    gboolean phrase_mode = gconf_client_get_bool(client, PHRASE_MODE_KEY, NULL);
	gboolean use_gsuggest = gconf_client_get_bool(client, USE_GOOGLE_SUGGEST_KEY, NULL);

    _use_google_suggest = (phrase_mode && use_gsuggest);

    if (!_use_google_suggest)
        soup_session_abort(_soupsession);
}

static void
phrase_mode_changed(GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer data)
{
    update_use_google_suggest();
}

static void
proxy_changed(GConfClient *client, guint cnxn_id, GConfEntry *entry, gpointer data)
{
    set_proxy(_soupsession);
}

// stolen from bug-buddy
static void
set_proxy(SoupSession *session)
{
    GConfClient *gconf_client;
    char *host;
    int port;
    char *proxy_uri;
    SoupUri *uri;
    char *username = NULL;
    char *password = NULL;

    gconf_client = gconf_client_get_default();

    if (gconf_client_get_bool(gconf_client, USE_PROXY_KEY, NULL) == FALSE) {
        g_object_unref(gconf_client);
        return;
    }

    host = gconf_client_get_string(gconf_client, PROXY_HOST_KEY, NULL);
    if (host == NULL) {
        g_object_unref(gconf_client);
        return;
    }
    port = gconf_client_get_int(gconf_client, PROXY_PORT_KEY, NULL);
    if (port == 0)
        port = 80;

    if (gconf_client_get_bool(gconf_client, USE_PROXY_AUTH, NULL)) {
        username = gconf_client_get_string(gconf_client, PROXY_HOST_KEY, NULL);
        password = gconf_client_get_string(gconf_client, PROXY_HOST_KEY, NULL);
    }

    if (username && password)
        proxy_uri = g_strdup_printf("http://%s:%s@%s:%d", username, password, host, port);
    else
        proxy_uri = g_strdup_printf("http://%s:%d", host, port);

    uri = soup_uri_new(proxy_uri);
    g_object_set(G_OBJECT(session), "proxy-uri", uri, NULL);
    
    g_free(host);
    g_free(username);
    g_free(password);
    g_free(proxy_uri);
    soup_uri_free(uri);
    g_object_unref(gconf_client);
}

// vi:ts=4:sw=4:nowrap:cin:expandtab
